//
// Created by u on 23-6-29.
//

#ifndef MOSQUITTO_TEST_MQTTMANAGER_HPP
#define MOSQUITTO_TEST_MQTTMANAGER_HPP

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <condition_variable>
#include <regex>
#include <thread>

#include <json.hpp>
#include <croncpp.h>

#include <MqttCommon.hpp>
#include <MqttClient.hpp>
#include <DeviceInfo.hpp>
#include <Task.hpp>
#include <Config.hpp>

static const std::vector<std::string> localSubTopicSet = {
	"dev/+/+/rack",
	"dev/+/+/wack",
	"dev/+/+/addack",
	"dev/+/+/delack",
	"dev/+/0/info",
	"dev/+/+/rlistack"
#if defined(TEST)
	,
	MQTT_PLATFORM "/" ProductKey "/" DeviceKey "/topo/change",
	MQTT_PLATFORM "/" ProductKey "/" DeviceKey "/actions/execute"
#endif
};

static const std::vector<std::string> cloudSubTopicSet = {
	MQTT_PLATFORM "/" ProductKey "/" DeviceKey "/topo/change",
	MQTT_PLATFORM "/" ProductKey "/" DeviceKey "/actions/execute"
};

template<typename F, class ...Args> 
auto Func(F f, Args&& ...args) {
	return f(std::forward<Args>(args)...);
}


class MqttManager {
public:
	MqttManager(const std::string& configPath) {

		mqttConfig_.reset(new Config(configPath));
		mqttConfig_->print();

		/* 泰智慧 */
//		cloudClient_.reset(new MqttClient("GID_WPanel_gateway@@@0107010C012202130000",
//			"mqtt-cn-9lb35537z01.mqtt.aliyuncs.com",
//			1883,
//			"Signature|LTAI5tSneHx9TLjmJKomRuAS|mqtt-cn-9lb35537z01",
//			"dWF5V6ixIC8IDeRSyV8hc1Sn2dI=",
//			cloudOnConnect,
//			cloudOnDisconnect,
//			onMessageStatic));
		/* iot 平台 	*/
		const MqttConnectInfo& cloudInfo = mqttConfig_->connectConfigInfos["cloud"];
		cloudClient_.reset(new MqttClient(
			cloudInfo.clientID,
			cloudInfo.server,
			cloudInfo.port,
			cloudInfo.user.c_str(),
			cloudInfo.passwd.c_str(),
			cloudInfo.cleanSession,
			cloudOnConnect,
			cloudOnDisconnect,
			onMessageStatic));

		const MqttConnectInfo& localInfo = mqttConfig_->connectConfigInfos["local"];
		localClient_.reset(new MqttClient(
			localInfo.clientID,
			localInfo.server,
			localInfo.port,
			localInfo.user.c_str(),
			localInfo.passwd.c_str(),
			localInfo.cleanSession,
			localOnConnect,
			localOnDisconnect,
			onMessageStatic));


		/* 连接服务器 */
		cloudClient_->connect(cloudInfo.keepAlive);
		localClient_->connect(localInfo.keepAlive);


		/* 线程函数 */
		/* mqtt 线程处理函数 */
		std::thread([&] {
		  parseMqttMsgThread();
		}).detach();

		std::thread([&] {
		  taskManager.init();
		  while (run_) {
			  taskManager.schedule();
		  }
		}).detach();
	}

	~MqttManager() = default;

private:
	/* Mqtt 函数 */
	static void parseMqttMsgThread() {
		while (run_) {
			std::unique_lock<std::mutex> lock(receiveMqttMutex_);
			receiveMqttCV_.wait(lock, [] { return isRead_; });
			isRead_ = false;

			while (!receiveMqttMsgQueue_.empty()) {
				const auto& msg = receiveMqttMsgQueue_.front();
				parseMsg(msg);
				receiveMqttMsgQueue_.pop();
			}
			
			// 添加延时操作，让出CPU执行时间给其他线程
        	std::this_thread::sleep_for(std::chrono::milliseconds(10));
		}
	}

	/* 云端连接回调函数 */
	static void cloudOnConnect(struct mosquitto*, void*, int rc) {
		if (rc == 0) {
			/* 订阅主题 */
			for (const auto& topic: cloudSubTopicSet) {
				cloudClient_->subscribe(topic);
			}
			cloudClientIsConnected_ = true;
			LOG_DEBUG("Connected to Cloud Mqtt broker.");
		}
		else {
			LOG_DEBUG("Connection failed: {}", mosquitto_strerror(rc));
			cloudClientIsConnected_ = false;
		}
	}

	/* 本地连接回调函数 */
	static void localOnConnect(struct mosquitto*, void*, int rc) {
		if (rc == 0) {
			/* 订阅主题 */
			for (const auto& topic: localSubTopicSet) {
				localClient_->subscribe(topic);
			}
			localClientIsConnected_ = true;
			LOG_DEBUG("Connected to Local Mqtt broker.");
			sendRlistReq();
		}
		else {
			localClientIsConnected_ = false;
			LOG_DEBUG("Connection failed: {}", mosquitto_strerror(rc));
		}
	}

	/* 云端断开连接回调函数 */
	static void localOnDisconnect(struct mosquitto*, void*, int rc) {
		localClientIsConnected_ = false;
		LOG_DEBUG("Disconnected from Local Mqtt broker.");
	}

	/* 本地断开连接回调函数 */
	static void cloudOnDisconnect(struct mosquitto*, void*, int rc) {
		cloudClientIsConnected_ = false;
		LOG_DEBUG("Disconnected from Cloud Mqtt broker.");
	}

	/* 本地、云端接收到消息后的回调函数 */
	static void onMessageStatic(struct mosquitto* mosq, void* obj, const struct mosquitto_message* msg) {
		updateMqttReceiveQueue({ msg->topic },
			{ static_cast<const char*>(msg->payload), static_cast<size_t>(msg->payloadlen) });
	}

	/* 更新 Mqtt 接收队列 */
	static void updateMqttReceiveQueue(const std::string& topic, const std::string& payload) {
		if (receiveMqttMsgQueue_.size() > kMqttQueueLength_) {
			receiveMqttMsgQueue_.pop();
		}
		receiveMqttMsgQueue_.emplace(topic, payload);
		std::lock_guard<std::mutex> _lck(receiveMqttMutex_);
		isRead_ = true;
		receiveMqttCV_.notify_one();
	}

	/** ------------------------------------------------------------------------------- */

	/* 判断是否为北向的消息 */
	static bool isNorthMsg(const std::string& str) {
		bool isNorthMsg{false};
		size_t distance1 = countDistance(str, '/');
		size_t distance2 = countDistance(str, '/', 1);
		LOG_DEBUG("distance1: {} distance2: {}", distance1, distance2);
		if ((str.find("actions") || str.find("topo")) && distance1 >= 6 && distance2 >= 20)
			isNorthMsg = true;
		return isNorthMsg;
	}

	/* 处理消息队列 */
	static bool parseMsg(const Msg& msg) {
		std::cout << msg << std::endl;

		if (isNorthMsg(msg.topic)) {
			/* 北向处理函数 */
			if (msg.topic.find("topo/change") != std::string::npos) {
				return cloudDownSendTopoAdd(msg);				/* 添加子设备拓扑结构*/
			} else if (msg.topic.find("actions") != std::string::npos) {
				return cloudDownSendDeviceControlMsgParse(msg);	/* 控制动作 */
			} else {

			}
		} else {
			/* 南向处理函数 */
			if ( msg.topic.find("dev_modbus_usb") != std::string::npos
				|| msg.topic.find("ack") != std::string::npos
				|| msg.topic.find("info") != std::string::npos) {
				return deviceReplyMsgParse(msg);				/* 本地消息处理 */
			} else {
				return true;
			}
		}
		return true;
	}

	/* 云端下发拓扑结构添加 */
	static bool cloudDownSendTopoAdd(const Msg& msg) {
		nlohmann::json j = nlohmann::json::parse(msg.payload);
		if (j.count("msgId")) {
			const auto& msgId = j.at("msgId");
		} else {
			LOG_ERROR("msgId isn't found.");
		};

		if (j.count("time")) {
			const auto& time = j.count("time");
		} else {
			LOG_ERROR("time isn't found.");
		}

		if (j.count("data")) {
			const auto& data = j.at("data");
			if (data.count("opt")) {
				const auto& opt = data.at("opt");
				if (opt == "scanDev") {
					LOG_DEBUG("opt is {}", opt.get<std::string>());
					// 此时构建消息，应主动发送组网消息，但组网消息上线后即发送（dev_canopen rlist），
					// 所以该接口有没有问题不大
				}
			} else {
				LOG_ERROR("opt isn't found.");
			}
		} else {
			LOG_ERROR("data isn't found.");
		};
		return true;
	}

	/* 云端下发控制消息处理 */
	static bool cloudDownSendDeviceControlMsgParse(const Msg& msg) {
		std::unordered_map<std::string, std::unordered_map<std::string, std::unordered_map<std::string, uint8_t>>>devsMap;
		const auto& j = nlohmann::json::parse(msg.payload);

		if (j.count("msgId")) {
			const auto& msgId = j.at("msgId");
		} else {
			LOG_ERROR("msgId isn't found." );
		};

		if (j.count("time")) {
			const auto& time = j.count("time");
		} else {
			LOG_ERROR("time isn't found." );
		}

		if (j.count("data") && j.at("data").count("devs")) {
			const auto& devs = j.at("data").at("devs");
			for (const auto& dev : devs) {
				if (dev.count("devKey") && dev.count("acts")) {
					const auto& devKey = dev.at("devKey");
					const auto& acts = dev.at("acts");
					std::unordered_map<std::string, std::unordered_map<std::string, uint8_t>> actsMap;
					for (const auto& act : acts) {
						if (act.count("code") && act.count("inParams")) {
							const auto& code = act.at("code");
							const auto& inParams = act.at("inParams");

							std::unordered_map<std::string, uint8_t> inParamMap;
							for (int i=0 ; i<inParams.size() ; i++ ) {
								if (inParams[i].count("unlock")) {
									LOG_DEBUG("devKey: {} code: {} unlock: {}", devKey, code, inParams[i].at("unlock").get<uint32_t>());
									inParamMap["unlock"] = inParams[i].at("unlock").get<uint32_t>();
								}

								if (inParams[i].count("open")) {
									LOG_DEBUG("devKey: {} code: {} open: {}", devKey, code, inParams[i].at("open").get<uint32_t>());
									inParamMap["open"] = inParams[i].at("open").get<uint32_t>();
								}

								if (inParams[i].count("close")) {
									LOG_DEBUG("devKey: {} code: {} close: {}", devKey, code, inParams[i].at("close").get<uint32_t>());
									inParamMap["close"] = inParams[i].at("close").get<uint32_t>();
								}
							}
							actsMap[code.get<std::string>()] = inParamMap;
						} else {
							LOG_ERROR("inParams isn't found." );
						}
					}
					devsMap[devKey.get<std::string>()] = actsMap;
				} else {
					LOG_ERROR("acts isn't found." );
				}
			}
		} else {
			LOG_ERROR( "data or devs isn't found.");
		}

		for (const auto& tDevMap : devsMap) {
			const auto& devSN = tDevMap.first;

			nlohmann::json resultJson{{"tick", 1}, {"time", getLocalTimeStampMs()}, {"src", "cloud_chint"}};
			nlohmann::json deviceJson;
			if (!deviceInfos.count(devSN)) {
				LOG_ERROR("Mapping of device id to sn does not exist!");
				LOG_ERROR("Please add the device first. SN: {}", devSN);
				return false;
			}
			deviceJson["id"] = deviceInfos.at(devSN).id;

			for (const auto& tActsMap : tDevMap.second) {
				const auto& actName = tActsMap.first;    // 断路器 breakerControl
				for (const auto& tActMap : tActsMap.second) {
					const auto& stats = tActMap.first;
					const auto& val = tActMap.second;
					LOG_DEBUG("stats: {} val:{}" , actName, val);
					if (stats == "unlock") {
						deviceJson["points"].push_back({{ "key", "Unlock"  }, { "val", val }});
					} else if (stats == "open") {
						deviceJson["points"].push_back({{ "key", "SwOpen"  }, { "val", val }});
					} else if (stats == "close") {
						deviceJson["points"].push_back({{ "key", "SwClose" }, { "val", val }});
					}
				}
			}
			resultJson["dev"]["devices"].push_back(deviceJson);
			std::string topic = "dev/cloud_chint/" + std::to_string(deviceInfos.at(devSN).id) + "/write";
			localClient_->publish(topic, resultJson.dump());
		}
		return true;
	}

	/* 设备（均指网关下的设备）回复消息处理 */
	static bool deviceReplyMsgParse(const Msg& msg) {
		LOG_DEBUG("deviceReplyMsgParse");
		if (msg.topic.find("/0/info") != std::string::npos) {
			return readReplyProcess(msg);
		} else if (msg.topic.find("wack") != std::string::npos) {
			return writeReplyProcess(msg);
		} else if (msg.topic.find("addack") != std::string::npos ||
		           msg.topic.find("rlistack") != std::string::npos) {
			return addDevsReplyProcess(msg);
		} else if (msg.topic.find("delack") != std::string::npos) {
			return delDevsReplyProcess(msg);
		} else {
			return false;
		}
	}

	/* 添加设备回复消息 */
	static bool addDevsReplyProcess(const Msg& msg) {
		// dev_canopen 上报添加拓扑信息
		LOG_DEBUG("addDevsReplyProcess");
		nlohmann::json j = nlohmann::json::parse(msg.payload);

		if (j.count("dev") && j.at("dev").count("devices") ||
			j.count("dev") && j.at("dev").count("tmp_devices")) {
			const auto& devices = j.at("dev").count("devices") ?
												 j.at("dev").at("devices") :
												 j.at("dev").at("tmp_devices");
			for (const auto & device : devices) {
				deviceInfo deviceInfo;
				if (device.count("id") && device.count("uuid")) {
					deviceInfo.sn = device.at("uuid").get<std::string>();
					deviceInfo.id = device.at("id").get<std::uint32_t>();

					if (device.count("type") && device.count("model")) {
						deviceInfo.type =  device.at("type").get<std::string>();
						deviceInfo.model = device.at("model").get<std::string>();
					}

					if (device.count("status")) {
						const std::string& status = device.at("status").get<std::string>();
						if (status == "success" || status == "failed" || status == "online"||status == "offline") {
							deviceInfo.status = status;
						}
					}
				}
				deviceInfos.insert(std::make_pair(deviceInfo.sn, deviceInfo));
			}
		}

		// 此处构建添加设备拓扑信息
		nlohmann::json topoJson;
		topoJson["msgId"] = getMsgId();
		topoJson["time"] = getLocalTimeStampMs();
		topoJson["data"]["total"] = 5;
		topoJson["data"]["pgNum"] = 1;
		topoJson["data"]["pgSize"] = 10;
		topoJson["data"]["records"] = 11;
		nlohmann::json& devsArray = topoJson["data"]["devs"];
		for (const auto& keyVal : deviceInfos) {
			nlohmann::json dev;
			dev["prodKey"] = keyVal.second.getProdKey();
			dev["devKey"] = keyVal.second.sn;
			devsArray.push_back(dev);
		}
		cloudClient_->publish(MQTT_TOPIC_DEVICE_TOPO_ADD, topoJson.dump());

		// 此处构建设备上下线信息
		nlohmann::json reportJson;
		reportJson["msgId"] = getMsgId();
		reportJson["time"] = getLocalTimeStampMs();
		for (const auto& keyVal : deviceInfos) {
			if (keyVal.second.status == "online" || keyVal.second.status == "offline") {
				const auto& onl = nlohmann::json{{ "val", keyVal.second.status == "online" ? 1 : 0 }};
				const auto& dev = nlohmann::json{{ "devKey", keyVal.second.sn }, {	"points",{ "onl", onl}}};
				reportJson["data"] = {{ "devs", { dev}}};
			}
		}
		cloudClient_->publish(MQTT_TOPIC_DEVICE_REPORT, reportJson.dump());
		return true;
	}

	/* 删除设备回复结构 */
	static bool delDevsReplyProcess(const Msg& msg) {
		LOG_DEBUG("delDevsReplyProcess");
		// dev_canopen 删除设备拓扑信息
		nlohmann::json json = nlohmann::json::parse(msg.payload);
		std::unordered_map<std::string, deviceInfo> toBeDeleted;
		if (json.count("dev") && json.at("dev").count("devices")) {
			const auto& devices = json.at("dev").at("devices");
			for (const auto & device : devices) {
				deviceInfo deviceInfo;
				if (device.count("id") && device.count("uuid")) {
					deviceInfo.sn = device.at("uuid").get<std::string>();
					deviceInfo.id = device.at("id").get<std::uint32_t>();

					if (device.count("type") && device.count("model")) {
						deviceInfo.type =  device.at("type").get<std::string>();
						deviceInfo.model = device.at("model").get<std::string>();
					}

					if (device.count("status")) {
						const std::string& status = device.at("status").get<std::string>();
						if (status == "success" || status == "failed" || status == "online"||status == "offline") {
							deviceInfo.status = status;
						}
					}
				}
				toBeDeleted.insert(std::make_pair(deviceInfo.sn, deviceInfo));
			}
		}

		// 此处构建删除设备拓扑结构
		nlohmann::json topoJson;
		topoJson["msgId"] = getMsgId();
		topoJson["time"] = getLocalTimeStampMs();
		topoJson["code"] = "00000";
		topoJson["data"]["total"] = 5;
		topoJson["data"]["pgNum"] = 1;
		topoJson["data"]["pgSize"] = 10;
		topoJson["data"]["records"] = 11;
		nlohmann::json& devsArray = topoJson["data"]["devs"];
		for (const auto& keyVal : toBeDeleted) {
			nlohmann::json dev;
			dev["prodKey"] = keyVal.second.getProdKey();
			dev["devKey"] = keyVal.second.sn;
			devsArray.push_back(dev);
		}
		cloudClient_->publish(MQTT_TOPIC_DEVICE_TOPO_DEL, topoJson.dump());
		return true;
	}

	/* 写设备回复消息 TODO */
	static bool writeReplyProcess(const Msg& msg) {
		LOG_DEBUG("writeReplyProcess");
		// dev_canopen 上报写信息
		nlohmann::json json = nlohmann::json::parse(msg.payload);
		if (json.count("dev") && json.at("dev").count("devices")) {
			const auto& devices = json.at("dev").at("devices");
			for (const auto & device : devices) {
				if (device.count("id")) {
					std::uint32_t id = device.at("id").get<std::uint32_t>();
					LOG_DEBUG("id: {}", id);
				}

				if (device.count("status")) {
					std::string status = device.at("status").get<std::string>();
					LOG_DEBUG("status: {}", status.c_str());
				}

				if (device.count("points")) {
					const auto& points = device.at("points");
					for (const auto & point : points) {
						if (point.count("key") && point.count("val") ) {
							std::string key = point.at("key");
							std::uint32_t val = point.at("val");
							LOG_DEBUG("key: {}  val: {}", key.c_str(), val);
						}
					}
				}
			}
		}
		return true;
	}

	/* 读设备回复消息 TODO */
	static bool readReplyProcess(const Msg& msg) {
		LOG_DEBUG("readReplyProcess");
		// dev_modbus_usb 上报读结果
		nlohmann::json json = nlohmann::json::parse(msg.payload);
		if (json.count("dev") && json.at("dev").count("points")) {
			std::unordered_map<std::string, uint32_t> keyVal;
			if (json.at("dev").count("status")) {
				const auto& status = json.at("dev").at("status");
				if (status == "online") {
					keyVal.insert(std::make_pair("onl", 1));
				} else if (status == "offline") {
					keyVal.insert(std::make_pair("onl", 0));
				}
			}
			const auto& points = json.at("dev").at("points");
			for (int j=0; j<points.size(); j++) {
				if (points.at(j).count("key") && points.at(j).count("val") ) {
					const std::string& key = points.at(j).at("key");
					const std::uint32_t& val = points.at(j).at("val");
					keyVal.insert(std::make_pair(key, val));
					LOG_DEBUG("key: {}  val: {}", key, val);
				}
			}
			nlohmann::json reportJson;
			reportJson["msgId"] = getMsgId();
			reportJson["time"] = getLocalTimeStampMs();
			nlohmann::json tmp;
			tmp["prodKey"] = ProductKey_SUB;
			tmp["devKey"]  = DeviceKey_SUB;
			for (const auto& pair : keyVal) {
				tmp["points"][pair.first]["val"] = pair.second;
				tmp["points"][pair.first]["rpt"] = getLocalTimeStampMs();
			}
			reportJson["data"]["devs"].push_back(tmp);
			cloudClient_->publish(MQTT_TOPIC_DEVICE_REPORT, reportJson.dump());
		}
		return true;
	}
	
	static bool sendRlistReq() {
		nlohmann::json json;
		json["tick"] = -1;
		json["time"] = getLocalTimeStampMs();
		json["src"] = "chint_cloud";
		json["dev"]["list"] = {{"offset", 0}, {"count", -1}};
		localClient_->publish("dev/chint_cloud/sys_agent/rlist",json.dump());
		return true;
	}

	/* */
	static void addTimedReport(const std::string& str) {
		std::thread([&]() {
		try {
			auto cron = cron::make_cron(str);
			while (1) {
				std::time_t now = std::time(0);
				std::time_t next = cron::cron_next(cron, now);  
				std::this_thread::sleep_until(system_clock::from_time_t(next));
				// 业务处理

			} 
		} catch (cron::bad_cronexpr const & ex){
			std::cerr << ex.what() << '\n';
		}
		}).detach();
	}

	static void fixedTimeReport_5s() {
		nlohmann::json dataJson;

		dataJson["msgId"] = getMsgId();
		dataJson["time"] = getLocalTimeStampMs();

		std::cout << "fixedTimeReport_5s: " << getLocalTimeStampS() << std::endl;
		const auto& Ua = nlohmann::json{{ "act", "get" }, { "val", 0 }, { "res", "OK" }};
		const auto& Ia = nlohmann::json{{ "act", "get" }, { "val", 0 }, { "res", "OK" }};
		const auto& Fre = nlohmann::json{{ "act", "get" }, { "val", 0 }, { "res", "OK" }};

		const auto& dev1 = nlohmann::json{{ "devKey", "01040701000000030001" }, {
			"points",
			{ "Ua", Ua },
			{ "Ia", Ia },
			{ "Fre", Fre }}
		};
		const auto& dev2 = nlohmann::json{{ "devKey", "01040701000000030001" }, {
			"points",
			{ "Ua", Ua },
			{ "Ia", Ia },
			{ "Fre", Fre }}
		};
		dataJson["dataJson"] = {{ "devs", { dev1, dev2 }}};

		cloudClient_->publish(MQTT_TOPIC_DEVICE_REPORT, dataJson.dump());
	}

	static const std::string& getMsgId() {
		static long counter;
		globalMsgId_ = std::to_string(counter);
		counter++;
		return globalMsgId_;
	}

private:
	static std::shared_ptr<MqttClient> cloudClient_;            /* mqtt cloud client */
	static std::shared_ptr<MqttClient> localClient_;            /* mqtt local client */
	static bool cloudClientIsConnected_;
	static bool localClientIsConnected_;

	MqttReceiveParseMsgCallback mqttReceiveParseMsgCB_;         /* mqtt msg parse function */
	MqttMsgParseInitCallback mqttMsgParseInitCB_;               /* mqtt fixed report function */
	std::vector<timedTaskStruct> timedTaskList_;
	std::mutex timedTaskListMutex_;
	static std::string globalMsgId_;

	static std::queue<Msg> receiveMqttMsgQueue_;                /* mqtt receive msg queue */
	static std::mutex receiveMqttMutex_;                        /* mqtt receive mutex */
	static std::condition_variable receiveMqttCV_;              /* mqtt receive condition variable */
	static bool isRead_;                                        /* prevent spurious wakeup */
	static const int kMqttQueueLength_ = 1024;                  /* mqtt receive queue max length */
	static bool run_;

	/* <sn, deviceInfo> */
	static std::unordered_map<std::string, deviceInfo> deviceInfos; /* device info */

	TaskManager taskManager;
	std::shared_ptr<Config> mqttConfig_;

};

std::queue<Msg> MqttManager::receiveMqttMsgQueue_;
std::mutex MqttManager::receiveMqttMutex_;
std::condition_variable MqttManager::receiveMqttCV_;
bool MqttManager::isRead_ = false;
std::shared_ptr<MqttClient> MqttManager::cloudClient_;
std::shared_ptr<MqttClient> MqttManager::localClient_;
bool MqttManager::cloudClientIsConnected_ { false};
bool MqttManager::localClientIsConnected_{ false};
std::string MqttManager::globalMsgId_;
bool MqttManager::run_{ true };
std::unordered_map<std::string, deviceInfo> MqttManager::deviceInfos;
#endif //MOSQUITTO_TEST_MQTTMANAGER_HPP
